using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using ProjectEuler.Core.Helpers;

namespace ProjectEuler.Core
{
    public class Problem61 : IProjectEulerProblem
    {
        public int Number
        {
            get { return 61; }
        }

        public string Description
        {
            get { return "Find the 6 cyclic numbers sum."; }
        }

        public string Answer
        {
            get { return FindPolygonalCyclicSet(GetMatrix()).Sum().ToString(); }
        }

        protected IList<int> FindPolygonalCyclicSet(List<List<int>> matrix)
        {
            var permrange = Enumerable.Range(0, matrix.Count).Select(n => n.ToString());
            var perm = new StringPerm(permrange);
            var perms = perm.EnumerateAllPermuations();
            var list = new List<List<int>>();
            foreach (var p in perms)
            {
                var indexArray = p.Select(i => int.Parse(i)).ToArray();
                list.AddRange(GetCyclicSets(indexArray, matrix));
            }
            return list.FirstOrDefault();
        }

        protected IEnumerable<List<int>> GetCyclicSets(int[] indexes, List<List<int>> matrix)
        {
            var cyclicset = (from a in matrix[indexes[0]]
                             from b in matrix[indexes[1]]
                             where IsCyclicalPair(a, b)
                             select new List<int> {a, b}).ToList();
            for (int i = 2; i < matrix.Count; i++)
            {
                cyclicset = BuildCyclicSets(cyclicset, matrix[indexes[i]]);
            }
            return cyclicset
                .Where(set => set.Count == matrix.Count)
                .Where(set => IsCyclicalPair(set[matrix.Count - 1], set[0]));
        }

        protected List<List<int>> BuildCyclicSets(List<List<int>> setA, List<int> setB)
        {
            return (from a in setA
                    from b in setB
                    where IsCyclicalPair(a.Last(), b)
                    select new List<int>(a.Union(new[] { b }))).ToList();
        }

        protected bool IsCyclicalPair(int a, int b)
        {
            string last2ofFirstNumber = a.ToString().Substring(2, 2);
            string first2ofSecondNumber = b.ToString().Substring(0, 2);
            return (last2ofFirstNumber == first2ofSecondNumber);
        }

        protected List<List<int>> GetMatrix()
        {
            var list = new List<List<int>>
                           {
                               GetAllFourDigitNumbers(Triangle),
                               GetAllFourDigitNumbers(Square),
                               GetAllFourDigitNumbers(Pentagonal),
                               GetAllFourDigitNumbers(Hexagonal),
                               GetAllFourDigitNumbers(Heptagonal),
                               GetAllFourDigitNumbers(Octagonal)
                           };
            return list;
        }

        protected int Triangle(int n)
        {
            return ((n*(n + 1))/2);
        }

        protected int Square(int n)
        {
            return (int) Math.Pow(n, 2);
        }

        protected int Pentagonal(int n)
        {
            return ((n*((3*n) - 1)))/2;
        }
        
        protected int Hexagonal(int n)
        {
            return n*((2*n) - 1);
        }

        protected int Heptagonal(int n)
        {
            return (n*((5*n) - 3))/2;
        }

        protected int Octagonal(int n)
        {
            return n*((3*n) - 2);
        }

        protected List<int> GetAllFourDigitNumbers(Func<int, int> polygonal)
        {
            var n = 1;
            var list = new List<int>();
            var result = polygonal(n);
            while(result < 10000)
            {
                n++;
                result = polygonal(n);
                if(result > 999) list.Add(result);
            }
            return list;
        }
    }

    [TestFixture]
    public class Problem61Tests : Problem61
    {
        [Test]
        public void Can_get_triangle_numbers()
        {
            Assert.AreEqual(1, Triangle(1));
            Assert.AreEqual(3, Triangle(2));
            Assert.AreEqual(6, Triangle(3));
            Assert.AreEqual(10, Triangle(4));
            Assert.AreEqual(15, Triangle(5));
        }

        [Test]
        public void Can_get_square_numbers()
        {
            Assert.AreEqual(1, Square(1));
            Assert.AreEqual(4, Square(2));
            Assert.AreEqual(9, Square(3));
            Assert.AreEqual(16, Square(4));
            Assert.AreEqual(25, Square(5));
        }
        
        [Test]
        public void Can_get_pentagonal_numbers()
        {
            Assert.AreEqual(1, Pentagonal(1));
            Assert.AreEqual(5, Pentagonal(2));
            Assert.AreEqual(12, Pentagonal(3));
            Assert.AreEqual(22, Pentagonal(4));
            Assert.AreEqual(35, Pentagonal(5));
        }
        
        [Test]
        public void Can_get_hexagonal_numbers()
        {
            Assert.AreEqual(1, Hexagonal(1));
            Assert.AreEqual(6, Hexagonal(2));
            Assert.AreEqual(15, Hexagonal(3));
            Assert.AreEqual(28, Hexagonal(4));
            Assert.AreEqual(45, Hexagonal(5));
        }

        [Test]
        public void Can_get_heptagonal_numbers()
        {
            Assert.AreEqual(1, Heptagonal(1));
            Assert.AreEqual(7, Heptagonal(2));
            Assert.AreEqual(18, Heptagonal(3));
            Assert.AreEqual(34, Heptagonal(4));
            Assert.AreEqual(55, Heptagonal(5));
        }

        [Test]
        public void Can_get_octagonal_numbers()
        {
            Assert.AreEqual(1, Octagonal(1));
            Assert.AreEqual(8, Octagonal(2));
            Assert.AreEqual(21, Octagonal(3));
            Assert.AreEqual(40, Octagonal(4));
            Assert.AreEqual(65, Octagonal(5));
        }

        [Test]
        public void Can_get_all_4_digit_numbers_via_the_polygonal_formulae()
        {
            IList<int> fourDigitTriangles = GetAllFourDigitNumbers(Triangle);
            Assert.That(fourDigitTriangles.Contains(8128));

            IList<int> fourDigitSquares = GetAllFourDigitNumbers(Square);
            Assert.That(fourDigitSquares.Contains(8281));
            
            IList<int> fourDigitPentagonals = GetAllFourDigitNumbers(Pentagonal);
            Assert.That(fourDigitPentagonals.Contains(2882));
        }

        [Test]
        public void Can_tell_if_a_pair_is_cyclical()
        {
            Assert.That(IsCyclicalPair(8128, 2882));
            Assert.That(IsCyclicalPair(2882, 8281));
            Assert.That(IsCyclicalPair(8281, 8128));
        }

        [Test]
        public void Can_find_cyclical_set_of_3()
        {
            var matrix = new List<List<int>>
                           {
                               GetAllFourDigitNumbers(Triangle),
                               GetAllFourDigitNumbers(Square),
                               GetAllFourDigitNumbers(Pentagonal)
                           };
            var cyclicset = FindPolygonalCyclicSet(matrix);
            Assert.That(cyclicset, Contains.Item(8128));
            Assert.That(cyclicset, Contains.Item(2882));
            Assert.That(cyclicset, Contains.Item(8281));
            Assert.That(cyclicset, Has.Count.EqualTo(3));
        }
    }
}